-- Copyright James McGill, 2010
-- Author: James McGill (jmcgill@plexer.net)

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity video_buffer is
    Port ( reset : in std_logic;
           clock_8mhz : in  STD_LOGIC;
           clock_24mhz : in  STD_LOGIC;
           hframe : in STD_LOGIC;
           vframe : in STD_LOGIC;
           data : in  STD_LOGIC_VECTOR (7 downto 0);
           address : out  STD_LOGIC_VECTOR (15 downto 0);
           pixel : out  STD_LOGIC_VECTOR (3 downto 0);
           initialized : out std_logic;
           buffer1 : out std_logic_vector (7 downto 0);
           buffer2 : out std_logic_vector (7 downto 0);
           buffer3 : out std_logic_vector (7 downto 0);
           buffer4 : out std_logic_vector (7 downto 0);
           shift_buffer_out : out std_logic_vector (7 downto 0);
           read_index_out : out std_logic_vector(7 downto 0);
           write_index_out : out std_logic_vector(7 downto 0);
			  buffer_count_out : out std_logic_vector(7 downto 0);
			  decrement_out : out std_logic);
end video_buffer;

architecture Behavioral of video_buffer is
  -- The number of elements to keep in the buffer.
  -- TODO(jmcgill): This buffer is getting *way* too big. Move to Dual Port RAM
  -- and clean up this design.
  constant buffer_size : integer := 30;  
 
  type byte_buffer_type is array(buffer_size downto 0) of std_logic_vector(7 downto 0);

  -- Four byte buffers used to cache pixel data before it is rendered.
  signal buffers : byte_buffer_type;

  -- The number of 8Mhz clock cycles, mod 4. This is used to ensure that
  -- data is read only during the high phase of the 2MHz clock.
  signal clock_counter : unsigned(2 downto 0) := to_unsigned(0, 3);

  -- The index of the buffer which is currently being written to.
  signal write_buffer_index : unsigned(7 downto 0) := to_unsigned(0, 8);

  -- The index of the buffer which is currently being read from.
  signal read_buffer_index : unsigned(7 downto 0) := to_unsigned(0, 8);

  -- The number of items currently in the buffer.
  signal buffer_count : unsigned(7 downto 0) := to_unsigned(0, 8);

  -- The next RAM address to read.
  signal internal_address : unsigned(15 downto 0) := to_unsigned(0, 16);

  -- A byte buffer for pixel data.
  signal shift_buffer : std_logic_vector(7 downto 0);

  -- The number of pixels which have been shifted out.
  signal shift_counter : unsigned(2 downto 0) := to_unsigned(0, 3);

  -- The number of clock pulses to assert decrement for. This works as a rough
  -- synchronization element between the two clocks.
  -- NOTE(jmcgill): This technique fails if clock_1_period / clock_2_period > 7
  
  signal decrement_counter : unsigned(3 downto 0) := to_unsigned(0, 4);
  
  signal running : std_logic := '0';
  
  -- HACK(jmcgill): This is used to prevent collisions in accessing the buffer
  -- count variable.
  signal decrement : std_logic;
begin
  read_index_out <= std_logic_vector(read_buffer_index);
  write_index_out <= std_logic_vector(write_buffer_index);
  
  -- NOTE(jmcgill): The 24 MHz and 8 Mhz clocks perform asynchronous access
  -- to the buffers without locking. This is safe as long as read_buffer_index
  -- and write_buffer_index are never equal.
  process (clock_8mhz)
  begin
    if (clock_8mhz'event and clock_8mhz = '1') then        
       -- if (reset = '0') then
          -- Only access RAM during the high phase of the 2MHz clock.
          --if (decrement = '1') then
			 --  buffer_count <= buffer_count - 1;
			 --decrement <= '0';
			 if (clock_counter < 4) then
			 --else
            if (buffer_count < buffer_size or decrement = '1') then    
              buffers(to_integer(write_buffer_index)) <= data;
              if (write_buffer_index = buffer_size - 1) then
                write_buffer_index <= to_unsigned(0, 8);
              else
                write_buffer_index <= write_buffer_index + 1;
              end if;            
              internal_address <= internal_address + 1;
              
				  if (decrement = '0') then
				    buffer_count <= buffer_count + 1;
				  else
				    buffer_count <= buffer_count;
				  end if;
				  
				  -- Gate access by other clock until buffer is half full. This only
				  -- needs to happen once.
				  if (buffer_count < 20 and running = '0') then
				    running <= '0';
				  else
				    running <= '1';
				  end if;
				end if;
			 elsif (decrement = '1') then
			   buffer_count <= buffer_count - 1; 
          end if;
        clock_counter <= clock_counter + 1;

        -- HACK(jmcgill): Reset address every row. 
        --if (hframe = '0' or vframe = '0') then
--          internal_address <= to_unsigned(0, 16);
        --end if;
     end if;
  end process;
--
  -- TODO(jmcgill): Pre-load the buffer to make this tots safe.
  process (clock_24mhz)
  begin
    if (clock_24mhz'event and clock_24mhz= '1') then
	   --shift_buffer <= std_logic_vector(unsigned(shift_buffer) + 1);
      -- Don't shift during hsync or vsync, as that (indirectly) causes new
      -- addresses to be loaded.
      if (hframe = '1' and vframe = '1' and running = '1') then 
        if (shift_counter = 0) then
			 shift_buffer <= data;
			 --shift_buffer <= buffers(to_integer(read_buffer_index));
          if (read_buffer_index = buffer_size - 1) then
            read_buffer_index <= to_unsigned(0, 8);
          else
            read_buffer_index <= read_buffer_index + 1;
          end if;
          -- Tell the 8Mhz clock handler to decrement the number of items in
          -- the buffer. We cannot write this directly as it may cause 
          -- conflict if two clock edges occur at the same time.			 
          decrement_counter <= to_unsigned(3, 4);
			 decrement <= '1';
        else
          shift_buffer <= '0' & shift_buffer(7 downto 1); 
        end if;
        shift_counter <= shift_counter + 1;
	    end if;
		 
		 -- Let the other process know that we have read an item from the buffer.
		 if decrement_counter > 0 then
		   decrement <= '1';
		   decrement_counter <= decrement_counter - 1;
		 else
		   decrement <= '0';
		 end if;
     end if;
  end process;

  address <= std_logic_vector(internal_address);
  pixel(0) <= shift_buffer(0);
  pixel(3 downto 1) <= "000";
  buffer1 <= buffers(0);
  buffer2 <= buffers(1);
  buffer3 <= buffers(2);
  buffer4 <= buffers(3);
  shift_buffer_out <= shift_buffer;
  buffer_count_out <= std_logic_vector(buffer_count);
  decrement_out <= decrement;
  
  -- Component is initialized once buffer is sufficiently full to prevent
  -- collisions.
  initialized <= '1' when (buffer_count > 2)
                 else '0';  
end Behavioral;
